Перейти к основному содержимому

5.03. История Java

Разработчику Архитектору

История Java

1. Предпосылки возникновения: технологический контекст начала 1990-х

К началу 1990-х годов доминирующими языками системного и прикладного программирования оставались C и C++. Первый обеспечивал близкую к железу производительность при высокой переносимости на уровне исходного кода; второй — выразительную объектно-ориентированную модель, но ценой усложнённой семантики (множественное наследование, перегрузка операторов, ручное управление памятью) и фрагментации компиляторов.

В то же время наблюдался быстрый рост числа аппаратных платформ: от серверов SPARC и x86 до встраиваемых процессоров, бытовой электроники и прототипов интерактивных телевизионных приставок. Переносимость уже не сводилась к «перекомпиляции на целевой архитектуре» — требовалась модель, где одна и та же программная единица могла бы исполняться без модификации на самых разных устройствах, включая те, архитектура которых ещё не была окончательно сформирована.

Одновременно назревал кризис в области безопасности. C и C++ изначально предполагали доверие к разработчику: возможность прямой адресной арифметики, отсутствие границ проверок массивов и неограниченный доступ к памяти делали программы уязвимыми к целому классу ошибок — buffer overflows, use-after-free, double-free. Эти уязвимости становились всё более критичными по мере роста сетевого взаимодействия устройств.

Именно в этом контексте в 1991 году в компании Sun Microsystems была запущена исследовательская инициатива под кодовым названием Green Project. Её официальной целью было создание программной платформы для «умной бытовой техники» — телевизоров, кофеварок, пультов управления, — но неявной, гораздо более амбициозной задачей стало выявление новых принципов построения языков и сред исполнения в условиях неопределённости платформы и повышенных требований к надёжности.

2. Green Project и рождение Oak: архитектурная лаборатория

Проект возглавил Джеймс Гослинг, чьи предыдущие работы в Sun (включая разработку Emacs-подобного редактора Gosling Emacs на C и создание оконной системы NeWS) давали ему уникальное понимание баланса между выразительностью, портируемостью и производительностью.

Изначально команда рассматривала C++ как основу, однако быстро обнаружила, что даже «чистый» C++ не позволяет достичь требуемых целей:

  • Фрагментация компиляторов делала невозможным гарантировать одинаковое поведение кода на разных устройствах;
  • Отсутствие встроенной безопасности требовало написания специализированных статических анализаторов и рантайм-обёрток;
  • Сложность модели памяти и ручное управление ресурсами создавали высокий барьер входа для разработчиков встраиваемых систем, где часто не было опыта системного программирования.

В результате был создан собственный язык — Oak («дуб»), названный в честь дерева, росшего за окном офиса Гослинга. Oak представлял собой гибридную попытку: сохранить C-подобный синтаксис (чтобы упростить переход для существующих разработчиков), но радикально упростить модель времени выполнения.

Ключевые архитектурные решения Oak:

  • Отказ от препроцессора. Макросы в C позволяли создавать «доменные языки», но вносили неопределённость в отладку и статический анализ.
  • Механизм сборки мусора с поколениями (generational GC), адаптированный под ограничения памяти встраиваемых систем. Это исключало необходимость явного вызова free() и делало программы более детерминированными в плане утечек.
  • Модель памяти с проверкой границ. Каждый доступ к массиву сопровождался неявной проверкой индекса; при нарушении генерировалось исключение, а не произвольная порча памяти.
  • Первый вариант виртуальной машины, названный Oak VM. В отличие от современной JVM, она изначально компилировала байт-код не только в интерпретируемом, но и в нативном режиме (с прямой генерацией машинного кода), что было важно для устройств без ресурсов под интерпретацию.

Тем не менее, коммерциализация Oak провалилась: рынок «умных» бытовых устройств в середине 1990-х не сформировался, а лицензионные ограничения на название Oak (существовала компания Oak Technology) вынудили к переименованию.

3. Переход к Java: от встраиваемых систем к интернету

В 1994–1995 годах в команде произошло осознание: хотя встраиваемые устройства не оправдали ожиданий, набирающая обороты технология World Wide Web нуждалась в интерактивных элементах, которые HTML 2.0 предоставить не мог. Браузеры тогда были статичными; плагины (например, для проигрывания видео) требовали установки, обладали низкой переносимостью и угрожали стабильности системы.

Команда Green Project перенастроила свои усилия: Oak был переработан под задачу «динамического контента в браузере». Новый язык получил название Java — нейтральное, запоминающееся, ассоциирующееся с энергией (кофеин), но не привязанное к технической семантике.

Важно отметить: термин Java изначально относился не к языку, а к платформе в целом — включая язык, виртуальную машину, библиотеки и среду исполнения. Это принципиальное отличие от C/C++, где язык и среда чётко разделены.

3.1. Философия «Write Once, Run Anywhere»: техническая реализация

Принцип WORA часто неверно трактуется как свойство языка. На деле он — следствие трёхуровневой архитектуры:

  1. Компилятор (javac) → генерирует байт-код (.class-файлы) — промежуточное представление, не зависящее от архитектуры процессора.
  2. Java Virtual Machine (JVM) → абстрактная машина со стековой архитектурой, определяющая:
    • формат класс-файлов,
    • набор инструкций (около 200 опкодов, большинство — загрузка/сохранение, арифметика, вызовы, управление потоком),
    • модель памяти (метод-область, куча, стек потока),
    • механизм загрузки классов (class loading),
    • систему безопасности (bytecode verifier, security manager).
  3. Реализация JVM под конкретную ОС/архитектуру (HotSpot от Sun/Oracle, OpenJ9 от IBM, GraalVM и др.) — преобразует байт-код в машинный код либо интерпретацией, либо JIT-компиляцией, либо AOT-компиляцией (в новых версиях).

Таким образом, переносимость обеспечивается стандартизацией промежуточного слоя, а не языковых конструкций. Это позволило сохранить статическую типизацию и компиляцию до выполнения (в отличие от, скажем, Python), но избежать привязки к ISA.

3.2. Безопасность как первичное требование

Апплеты (мини-приложения, встраиваемые в HTML через <applet>) должны были запускаться на клиентской машине без ведома пользователя и без привилегий. Поэтому архитектура безопасности была заложена глубоко в JVM:

  • Bytecode verifier — статический анализатор, проверяющий байт-код до загрузки в JVM на соответствие контрактам: корректность стека, типобезопасность, отсутствие запрещённых инструкций (напр., jsr в определённых контекстах).
  • Security manager — динамический контроллер, отвечающий за проверку разрешений во время выполнения (доступ к файлу, сокету, системным свойствам). Политики задавались внешними файлами (java.policy).
  • Песочница (sandbox) — набор ограничений по умолчанию для неподписанных апплетов: запрет на запись в файловую систему, запрет на подключение к сторонним хостам (кроме домена, с которого загружен апплет), запрет на вызов System.exit().

Эти механизмы сделали Java одной из первых платформ, где безопасность рассматривалась как свойство среды исполнения, а не ответственность разработчика.

4. Java 1.0 (1996): первая релизная версия

Релиз состоялся 23 января 1996 года. Пакет дистрибутива включал:

  • Компилятор javac,
  • Интерпретатор java,
  • Дебаггер jdb,
  • Библиотеку классов (около 210 классов в 8 пакетах: java.lang, java.io, java.util, java.net, java.awt, java.applet, java.awt.image, java.awt.peer),
  • Applet Viewer — автономную среду для тестирования апплетов без браузера.

Синтаксически Java 1.0 была уже близка к современному виду, но с рядом ограничений:

  • Отсутствие ключевого слова strictfp (добавлено в 1.2),
  • Нет коллекций — вместо List, Map использовались Vector, Hashtable, Stack (все синхронизированы по умолчанию),
  • Интерфейсы не могли содержать константы (хотя технически static final поля были разрешены, их использование считалось антипаттерном),
  • Исключения — только Throwable, Exception, RuntimeException, Error; собственная иерархия ошибок почти отсутствовала.

Особое внимание уделялось модель многопоточности: Java стала первым массовым языком, где потоки (Thread) были встроенными в язык (ключевое слово synchronized, методы wait()/notify() в Object). Это предвосхитило эпоху многопроцессорных систем и сетевых серверов.

5. Java 1.1 (1997): фундамент для зрелости

Релиз 18 февраля 1997 года стал первым крупным обновлением. Здесь были устранены наиболее критичные ограничения 1.0:

  • Внутренние классы — позволили реализовывать замыкания (до появления лямбд) и удобные шаблоны (например, ActionListener как анонимный класс). Важно: внутренний класс не является просто синтаксическим сахаром — он порождает отдельный .class-файл и сохраняет неявную ссылку на экземпляр внешнего класса (this$0).
  • Рефлексия (java.lang.reflect) — механизм интроспекции типов во время выполнения. Впервые стала возможна загрузка классов по имени (Class.forName()), получение метаданных, вызов методов динамически. Это заложило основу для фреймворков (Spring, Hibernate), построенных на внедрении зависимостей и маппинге.
  • Сериализация — стандартный механизм преобразования объектов в поток байтов (Serializable), с возможностью кастомизации (readObject/writeObject). Хотя сегодня её считают устаревшей (из-за проблем безопасности и версионирования), в 1997 году это был прорыв для RMI и сохранения состояния.
  • RMI (Remote Method Invocation) — первая встроенная поддержка распределённых вычислений: вызов методов на удалённых объектах как локальных. Основан на сериализации и динамической загрузке классов.
  • Улучшенная AWT — новая событийная модель (delegation event model), заменившая унаследованную от C++ «модель наследования», где переопределялись методы вроде action().

Java 1.1 стала первой версией, в которой язык начал использоваться для реальных enterprise-проектов, несмотря на отсутствие официальной «Enterprise Edition». Многие паттерны, позже ставшие классическими (DAO, Factory, Observer в виде PropertyChangeListener), были реализованы именно на базе 1.1.


6. Java 1.2 и Java 2 Platform: становление экосистемы (1998–2000)

Выпуск Java 1.2 в декабре 1998 года ознаменовал не просто инкрементальное обновление, а стратегическую реконфигурацию всей платформы. Sun Microsystems официально представила концепцию Java 2 Platform, разделив экосистему на три независимые, но совместимые редакции:

  • J2SE (Java 2 Standard Edition) — целевая платформа для настольных и серверных приложений среднего масштаба;
  • J2EE (Java 2 Enterprise Edition) — надстройка над J2SE для распределённых, многопользовательских систем с требованиями к надёжности, масштабируемости и транзакционной целостности;
  • J2ME (Java 2 Micro Edition) — облегчённая версия для устройств с ограниченными ресурсами: сотовых телефонов, пейджеров, карманных ПК.

Такое разделение позволило избежать «раздувания» ядра: J2SE оставалась относительно компактной, в то время как J2EE и J2ME развивались автономно, адаптируясь к специфике своих доменов. Это также легитимизировало Java как универсальную технологию — от микроконтроллеров до мэйнфреймов.

6.1. Технические вехи Java 1.2
  • Система пакетов и JAR-файлов. Хотя пакеты существовали с 1.0, именно в 1.2 была стандартизирована иерархия java.*, javax.*, а также введён формат JAR (Java ARchive) — ZIP-контейнер с метаданными (META-INF/MANIFEST.MF), позволяющий упаковывать классы, ресурсы и зависимости в единый артефакт. Это стало критически важным для распространения библиотек и апплетов.

  • Swing — замена AWT. Абстрактная оконная библиотека Swing (javax.swing) была построена целиком на Java, без нативных вызовов («lightweight» компоненты), что обеспечило кросс-платформенную согласованность внешнего вида и поведения. Архитектура MVC (Model-View-Controller) была явно выделена: например, JTable разделяла модель данных (TableModel), отображение (TableCellRenderer) и обработку событий. Swing также представил Pluggable Look-and-Feel (PLAF) — механизм динамической смены оформления без перекомпиляции.

  • Just-In-Time (JIT) компиляция в HotSpot. Первая реализация HotSpot JVM (первоначально разработанная компанией Longview Technologies, приобретённой Sun в 1997) включала адаптивный JIT-компилятор, который анализировал профиль выполнения и оптимизировал «горячие» участки кода во время работы. В отличие от простых JIT-ов, HotSpot использовал интерпретацию на старте и отложенную компиляцию, что позволяло собрать статистику вызовов, типов аргументов и ветвлений — и на её основе применять агрессивные оптимизации: inlining, escape analysis, loop unrolling. Это сократило разрыв в производительности с нативными приложениями с 10–20× до 1.5–3×, сделав Java приемлемой для высоконагруженных серверов.

  • Коллекции (Collections Framework). Введённый в java.util фреймворк (List, Set, Map, Iterator) стандартизировал операции над структурами данных. Ключевым решением стало разделение интерфейсов и реализаций (ArrayList vs LinkedList, HashMap vs TreeMap), а также введение fail-fast итераторов, которые детектировали модификацию коллекции во время обхода и выбрасывали ConcurrentModificationException. Это значительно повысило предсказуемость поведения и облегчило отладку.

  • Управление памятью: Soft, Weak, Phantom References. Помимо StrongReference (обычное удержание объекта), появились классы SoftReference, WeakReference, PhantomReference, позволяющие тонко регулировать взаимодействие с GC. Например, WeakHashMap использует WeakReference для ключей — запись автоматически исчезает, когда ключ становится недостижимым, что идеально подходит для кешей.

6.2. J2EE: институционализация enterprise-разработки

Хотя J2EE 1.2 вышел лишь в декабре 1999 года, его основные спецификации — EJB (Enterprise JavaBeans), JDBC (Java Database Connectivity), JNDI (Java Naming and Directory Interface), JMS (Java Message Service) — были объявлены ещё в рамках Java 1.2. Эти API заложили фундамент контейнерной архитектуры:

  • EJB — компонентная модель, где бизнес-логика инкапсулировалась в бины (Session Beans, Entity Beans), управляемые контейнером. Контейнер брал на себя: транзакции (@TransactionAttribute), безопасность (@RolesAllowed), пул потоков, пассивацию/активацию. Это был ответ на рост сложности распределённых систем, где разработчики тратили больше времени на инфраструктурные задачи, чем на бизнес-логику.

  • JDBC — унифицированный интерфейс к реляционным СУБД. В отличие от ODBC (C-based), JDBC был чисто Java, с чёткими уровнями изоляции транзакций (READ_COMMITTED, REPEATABLE_READ и т.д.) и параметризованными запросами, защищающими от SQL-инъекций. Драйверы реализовывались как JAR-файлы, подключаемые динамически через Class.forName().

  • JNDI — служба имён, позволяющая регистрировать и извлекать ресурсы (пулы соединений, EJB, очереди JMS) по логическим именам, а не хардкодить физические адреса. Это обеспечивало конфигурируемость без перекомпиляции.

На практике J2EE стал синонимом «тяжеловесной» архитектуры: разработка требовала генерации десятков XML-описаний (ejb-jar.xml, web.xml), разворачивания в application server (WebLogic, WebSphere), и сопровождалась значительным overhead’ом. Тем не менее, именно J2EE стандартизировал индустрию: появилось понятие Java-разработчика enterprise-уровня, а рынок серверов вырос многократно.

7. Эпоха Java 5 (J2SE 5.0, 2004): лингвистическая реформа

После относительно скромных обновлений в Java 1.3 (2000) и 1.4 (2002) — в основном, расширение библиотек (java.nio, регулярные выражения, логгинг) и улучшение GC — релиз Java 5.0 (внутренний номер 1.5, но маркетингово — «5») стал крупнейшей эволюцией языка с момента 1.0. Sun осознала: бойлерплейт-код, ручное управление типами и отсутствие метапрограммирования тормозят продуктивность.

7.1. Дженерики (Generics)

Синтаксис <T> позволил сделать коллекции типобезопасными на этапе компиляции. Ранее:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // ClassCastException при ошибке

Теперь:

List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // проверка на этапе компиляции

Важно: дженерики реализованы через type erasure — информация о типе-параметре стирается при компиляции, и в байт-коде остаётся только List. Это гарантирует обратную совместимость (бинарную), но лишает рантайм-доступа к типу (например, new T() невозможно без рефлексии). Выбор в пользу erasure, а не reification (как в C#), был сделан сознательно: чтобы все существующие .class-файлы продолжали работать без перекомпиляции.

7.2. Аннотации (Annotations)

Механизм метаданных, внедряемых прямо в исходный код:

@Override
public String toString() { ... }

@Deprecated
public void oldMethod() { ... }

@Transactional
public void transfer() { ... }

Аннотации не изменяют семантику программы сами по себе — их интерпретируют процессоры: компилятор (проверка @Override), рантайм (через рефлексию — @Transactional в Spring), или внешние инструменты (JAXB, JPA). Это заложило основу для декларативного программирования: вместо императивного кода управления транзакциями — одна аннотация.

7.3. Автоматическая упаковка/распаковка (Autoboxing/Unboxing)

Устранение рутины при работе с примитивами и их объектными аналогами:

Integer i = 42;          // autoboxing: int → Integer
int j = i; // unboxing: Integer → int
List<Integer> list = Arrays.asList(1, 2, 3); // без явных new Integer(...)

Под капотом компилятор вставляет вызовы Integer.valueOf() и intValue(). Кэширование значений от –128 до 127 (для Integer) обеспечивает экономию памяти и идентичность ссылок в пределах диапазона.

7.4. Перечисления (Enums)

До Java 5 использовались public static final int константы — с риском подмены значения и отсутствием типобезопасности. Новый тип enum:

public enum Day { MONDAY, TUESDAY, WEDNESDAY; }

public enum Status {
PENDING(0), APPROVED(1), REJECTED(2);

private final int code;
Status(int code) { this.code = code; }
public int getCode() { return code; }
}

— это полноценные классы: могут иметь поля, методы, реализовывать интерфейсы, содержать внутренние классы. Компилятор генерирует private static final экземпляры и запрещает наследование.

7.5. Пакет java.util.concurrent

До Java 5 многопоточность сводилась к synchronized, wait()/notify(). Это позволяло писать корректный код, но требовало глубокого понимания модели памяти и было подвержено deadlock’ам и livelock’ам.

Брайан Гётц и команда разработали высокоуровневые абстракции:

  • ExecutorService — пул потоков с управляемым жизненным циклом;
  • Future<T> и Callable<T> — асинхронные вычисления с возвратом результата;
  • ConcurrentHashMap, CopyOnWriteArrayList — потокобезопасные коллекции без глобальной блокировки;
  • ReentrantLock, Semaphore, CountDownLatch, CyclicBarrier — примитивы синхронизации, более гибкие, чем synchronized;
  • AtomicInteger, AtomicReference — операции compare-and-set (CAS) на аппаратном уровне.

Это стало фундаментом для масштабируемых серверов и реактивных систем.

8. Java 6 и Java 7: стабилизация и инфраструктурная зрелость

  • Java 6 (2006): в основном фокус на JVM и инструментарий. HotSpot получил улучшенный сборщик мусора (Parallel GC), поддержку отладки через JMX, интеграцию с нативными библиотеками (JNI improvements), и встроенную поддержку скриптовых языков (JSR 223 — ScriptEngineManager). Java стала не только языком, но и платформой для интеграции.

  • Java 7 (2011): после пятилетнего перерыва (связанного с внутренними дискуссиями в Sun и переходом к Oracle) вышли умеренные, но практичные улучшения:

    • Multi-catch и rethrow улучшённого типа:
      try { ... }
      catch (IOException | SQLException e) { ... } // один блок для нескольких исключений
    • Try-with-resources — автоматическое освобождение ресурсов, реализующих AutoCloseable:
      try (FileInputStream fis = new FileInputStream("data.bin");
      BufferedInputStream bis = new BufferedInputStream(fis)) {
      // ...
      } // fis и bis закрываются автоматически, даже при исключении
    • Diamond-оператор (<>) — сокращение синтаксиса дженериков:
      Map<String, List<Integer>> map = new HashMap<>(); // тип выводится из контекста
    • NIO.2 (java.nio.file) — современный API для работы с файловой системой: Path, Files, WatchService (мониторинг изменений), поддержка символических ссылок.

Java 7 продемонстрировала смену приоритетов: от лингвистических инноваций — к инженерной надёжности, производительности JVM и улучшению developer experience (DX).

9. Java 8 (2014): функциональный поворот и модернизация ядра

Релиз марта 2014 года стал, возможно, самым значительным после Java 5. Он изменил не только синтаксис, но и стиль программирования в экосистеме.

9.1. Лямбда-выражения

Синтаксис (args) -> body позволил писать код в декларативном, функциональном стиле:

button.addActionListener(e -> System.out.println("Clicked"));

List<String> names = users.stream()
.filter(u -> u.isActive())
.map(User::getName)
.sorted()
.collect(Collectors.toList());

Технически лямбды компилируются в статические приватные методы + вызов invokedynamic (инструкция байт-кода, добавленная в JVM в Java 7 специально для динамических языков и лямбд). Это обеспечивает низкий overhead по сравнению с анонимными классами.

9.2. Stream API

java.util.stream предоставил единый интерфейс для последовательной или параллельной обработки потоков данных:

  • Промежуточные операции (filter, map, sorted) — ленивые, возвращают новый Stream;
  • Терминальные операции (collect, forEach, reduce) — запускают вычисление.

Stream API не просто сокращает код — он позволяет JVM применять оптимизации: fusion (слияние map().filter() в один проход), loop unrolling, vectorization через ForkJoinPool.

9.3. Default и static методы в интерфейсах

Ранее интерфейсы могли содержать только абстрактные методы и static final поля. Java 8 разрешила:

public interface Comparator<T> {
int compare(T o1, T o2);

default Comparator<T> reversed() {
return (o1, o2) -> compare(o2, o1);
}

static <T> Comparator<T> comparing(Function<T, ?> keyExtractor) { ... }
}

Это решило проблему эволюции интерфейсов: можно добавлять методы без нарушения совместимости с существующими реализациями. Default-методы также стали основой для поведенческого наследования (composition over inheritance).

9.4. Модель даты и времени — java.time

Устаревшие Date (изменяемый, без временных зон) и Calendar (громоздкий, непотокобезопасный) были заменены иммутабельными, потокобезопасными типами:

  • LocalDate, LocalTime, LocalDateTime — без временной зоны;
  • ZonedDateTime, OffsetDateTime — с зоной или смещением;
  • Duration, Period — для измерения интервалов;
  • DateTimeFormatter — неизменяемый, потокобезопасный парсер/форматтер.

Архитектура основана на JSR-310 (проект ThreeTen Брюса Тэка), вдохновлённом Joda-Time, но переписанном с нуля для интеграции в JDK.

9.5. Nashorn и invokedynamic

Встроенный JavaScript-движок Nashorn (заменяющий Rhino) использовал invokedynamic для JIT-компиляции скриптов в байт-код, достигая производительности, сопоставимой с V8. Хотя Nashorn был удалён в Java 15, его наследие — доказательство гибкости JVM как платформы для динамических языков.


10. Java 9 (2017): модульная система и новая модель релизов

Релиз сентября 2017 года стал поворотным не только по содержанию, но и по процессу: начиная с Java 9, компания Oracle (после приобретения Sun в 2010 г.) перешла к предсказуемому 6-месячному циклу выпусков, с обязательным релизом каждую весну (март) и осень (сентябрь). Одновременно была введена концепция LTS (Long-Term Support): каждая шестая версия (начиная с Java 11) получает коммерческую и community-поддержку в течение минимум трёх лет (для Oracle — до 8 лет при paid support), в то время как промежуточные версии поддерживаются лишь до выхода следующей.

Это решение отражало изменение экономики open source: enterprise-клиенты требовали стабильности, в то время как разработчики хотели быстрого доступа к новым возможностям. Модель LTS/interim позволила разделить эти потоки.

10.1. Project Jigsaw: модульная система (JPMS — Java Platform Module System)

Наиболее амбициозное изменение с Java 5 — введение модулей через ключевое слово module и дескриптор module-info.java:

module com.example.app {
requires java.sql;
requires transitive org.slf4j;
exports com.example.core;
exports com.example.utils to com.example.web;
opens com.example.config to com.fasterxml.jackson.databind;
}

Цели JPMS:

  • Явное управление зависимостями: модуль декларирует, какие другие модули ему требуются (requires), и какие свои пакеты он экспортирует (exports). Всё, что не экспортировано, недоступно извне — даже через рефлексию (если не указано opens).
  • Упрочнение инкапсуляции JDK: ранее все public классы в rt.jar были доступны. Теперь, например, sun.misc.Unsafe находится в модуле jdk.unsupported, и его использование требует явного --add-opens.
  • Сборка кастомных runtime-образов с помощью jlink: позволяет создавать минимальные JVM-дистрибутивы, содержащие только нужные модули (например, 30 МБ вместо 200 МБ), что критично для контейнеризации.

На практике внедрение JPMS оказалось сложным. Многие библиотеки (включая Spring и Hibernate) полагались на рефлексию и внутренние API JDK, что привело к необходимости широкого использования --add-opens и --add-exports. Тем не менее, JPMS заложил основу для масштабируемой архитектуры: сегодня Java поддерживает системы из сотен модулей (например, IntelliJ IDEA), где зависимости строго верифицируются на этапе компиляции и линковки.

10.2. Другие ключевые изменения Java 9
  • JShell — REPL (Read-Eval-Print Loop) для интерактивного выполнения Java-кода без создания классов. Стал незаменимым инструментом для обучения и прототипирования.
  • Улучшенная система логгирования JVM (-Xlog), унифицирующая вывод GC, JIT, class loading в единый формат с гибкой фильтрацией.
  • HTTP/2 Client (Incubator) — первый шаг к замене устаревшего HttpURLConnection. Позже стабилизирован в Java 11.

11. Java 10–16: постепенная эволюция и подготовка к прорывам

Эти версии, хотя и не LTS, сыграли важную роль в адаптации платформы:

  • Java 10 (2018): ввёл Local-Variable Type Inference (var):

    var list = new ArrayList<String>(); // тип выводится как ArrayList<String>
    var stream = list.stream(); // Stream<String>

    Важно: var — не динамическая типизация; тип фиксируется на этапе компиляции. Ограничения: недопустим в полях, параметрах, возвращаемых типах — только в локальных переменных с инициализатором.

  • Java 11 (2018, LTS): первый LTS-релиз по новой модели. Ключевые изменения:

    • Удаление устаревших модулей: java.se.ee (CORBA, JAXB, JAX-WS), что отразило переход от monolithic J2EE к микросервисам и cloud-native.
    • Стабилизация HTTP Client API (на замену Apache HttpClient и OkHttp в стандартной библиотеке):
      HttpClient client = HttpClient.newHttpClient();
      HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.example.com"))
      .header("Accept", "application/json")
      .build();
      HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
    • Поддержка TLS 1.3, Epsilon GC (no-op сборщик — для кратковременных задач и бенчмаркинга).
  • Java 12–13: инкубационные фичи: Switch Expressions (упрощение switch), Text Blocks (многострочные строки, """...""").

  • Java 14 (2020): Records — компактное объявление неизменяемых классов-носителей данных:

    public record Point(int x, int y) {}
    // Компилятор генерирует: private final поля, конструктор, equals, hashCode, toString

    Records не являются «кортежами» или структурами — это полноценные классы, наследующие java.lang.Record, с возможностью добавления методов и реализации интерфейсов.

  • Java 15 (2020): Sealed Classes — контроль над иерархией наследования:

    public sealed class Shape permits Circle, Rectangle, Triangle { ... }
    // Только перечисленные классы могут наследоваться от Shape

    Это позволяет компилятору проверять полноту instanceof-цепочек и обеспечивает закрытые домены типов — основу для будущего pattern matching.

  • Java 16 (2021): Pattern Matching for instanceof:

    if (obj instanceof String s && s.length() > 5) {
    System.out.println(s.toUpperCase()); // s уже String, без каста
    }

    Устраняет шаблон String s = (String) obj; и повышает безопасность.

12. Java 17 (2021, LTS): консолидация и промышленная зрелость

Java 17 стал первым LTS, собравшим все лингвистические инновации предыдущих версий (Records, Sealed Classes, Text Blocks, Pattern Matching частично) в стабильном виде. Это позволило enterprise-компаниям перейти с Java 8/11 на современный Java без рисков инкубационных API.

Особое значение имеет удаление Applet API — официальное признание конца эры клиент-ориентированных Java-приложений в браузере. Платформа окончательно сместилась в сторону серверной, облачной и data-intensive разработки.

Также в Java 17:

  • Strong encapsulation by default — модули JDK теперь строго закрыты; доступ к внутренностям требует явных --add-opens.
  • Новые алгоритмы шифрования (EdDSA, X25519), поддержка ZGC и Shenandoah GC (low-pause сборщики) как production-ready.

13. Java 18–21: путь к «умной» Java

13.1. Project Loom: виртуальные потоки (Virtual Threads)

Стабилизированы в Java 21 (2023, LTS). Традиционные потоки (java.lang.Thread) — это обёртка над нативными потоками ОС, которые дороги по памяти (1 МБ стека) и количеству (ограничены ядром). Виртуальные потоки — лёгкие потоки, управляемые JVM, с переключением в пользовательском пространстве:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
// Каждая задача — в отдельном виртуальном потоке
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
// 10 000 параллельных задач на одном ядре

Это позволяет писать синхронный код для асинхронных I/O-нагруженных систем (веб-серверы, микросервисы), избегая callback-адского и реактивного бойлерплейта. Проект Loom меняет модель масштабируемости: от thread-per-connectionvirtual-thread-per-request.

13.2. Project Panama: Foreign Function & Memory API (FFM)

Стабилизирован в Java 22 (но доступен с 19 как preview), FFM заменяет устаревший, небезопасный JNI. Он позволяет:

  • Вызывать нативные функции (C, C++, Rust) без генерации glue-кода;
  • Управлять off-heap памятью (например, для zero-copy обработки сетевых пакетов или работы с GPU);
  • Работать с MemorySegment, MemoryLayout, Linker — типобезопасно и без утечек.

Пример:

Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle printf = stdlib.find("printf").get()
.asType(MethodType.methodType(int.class, MemorySegment.class));
try (Arena arena = Arena.ofConfined()) {
MemorySegment str = arena.allocateUtf8String("Hello from Java %d\n");
printf.invoke(str, 42);
}

Panama открывает Java для high-performance domain’ов: машинное обучение (интеграция с CUDA), системное программирование, embedded.

13.3. Project Valhalla: Value Types и Generic Specialization

Находится в активной разработке (preview в Java 21). Цель — преодолеть разрыв между примитивами и объектами:

  • Value Types (inline class) — объекты без идентичности (== по значению, а не по ссылке), размещаемые внутри других объектов («flattening»), что устраняет indirection и повышает кэш-локальность.
  • Generic Specialization — возможность параметризовать дженерики примитивами: List<int> вместо List<Integer>, без boxing/unboxing и аллокаций.

Это критично для научных вычислений, финансовых систем, игр — где каждый такт и байт важен.

14. Экосистема: OpenJDK, сообщество и конкуренция

С 2006 года, когда Sun открыл исходный код Java под GPL (OpenJDK), платформа перестала быть монополией одного вендора. Сегодня:

  • OpenJDK — reference implementation, поддерживаемая сообществом (Red Hat, SAP, Amazon, Microsoft, Alibaba).
  • Дистрибутивы: Adoptium (Eclipse Temurin), Amazon Corretto, Azul Zulu, Microsoft Build of OpenJDK — все совместимы, но различаются по GC, security patches, LTS-политике.
  • GraalVM — high-performance runtime с AOT-компиляцией (native-image), полиморфной JIT и поддержкой JavaScript, Python, Ruby, R на одной VM.
  • Конкуренция:
    • Kotlin — язык на JVM, решающий многие боли Java (null safety, extension functions, concise syntax), официально поддерживаемый Google для Android.
    • Go — для облачных микросервисов (лёгкие горутины, простой deployment).
    • Rust — для системного кода (memory safety без GC).

Java адаптируется: ускоряется цикл фич, упрощается синтаксис, улучшается GC. Но её преимущество — не в скорости инноваций, а в стабильности, зрелости библиотек и экосистемы: Spring, Jakarta EE, Quarkus, Micronaut, Kafka, Hadoop, Spark — всё это работает на JVM.

15. Критический анализ: достижения и вызовы

Достижения:

  • Платформенная независимость, реализованная через JVM, остаётся непревзойдённой по масштабу.
  • Безопасность как первичное требование заложила основы современных sandbox-моделей.
  • Эволюционный подход: обратная совместимость сохраняется более 25 лет — .class-файлы 1996 года исполняются на JVM 21.
  • Масштабируемость: от микросервисов (Quarkus, 20 МБ) до big data (Spark, 1000+ узлов).

Вызовы:

  • Сложность GC-тюнинга: выбор между G1, ZGC, Shenandoah требует экспертизы.
  • Задержки внедрения фич: Records появились в 2020, хотя обсуждались с 2009.
  • Legacy-ограничения: type erasure, отсутствие property syntax, необходимость main в отдельном классе.
  • Вес JDK: даже после jlink, образы крупнее, чем у Go/Rust.